package com.xqboss.apps.service.marketing;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.constant.RedisKey;
import com.xqboss.apps.domain.box.Box;
import com.xqboss.apps.domain.marketing.MarketingAirdrop;
import com.xqboss.apps.domain.marketing.MarketingAirdropMaterial;
import com.xqboss.apps.domain.marketing.MarketingAirdropMaterialRecord;
import com.xqboss.apps.domain.marketing.MarketingAirdropRecord;
import com.xqboss.apps.domain.nft.Nft;
import com.xqboss.apps.dto.marketing.AddAirdropDto;
import com.xqboss.apps.dto.marketing.WebAirdropMaterialDto;
import com.xqboss.apps.dto.marketing.WebAirdropMaterialUserDto;
import com.xqboss.apps.dto.user.ChangeUserWalletDto;
import com.xqboss.apps.enums.marketing.AirdropStatusEnum;
import com.xqboss.apps.enums.marketing.AirdropUserTypeEnum;
import com.xqboss.apps.enums.mq.AppMqDelayEnum;
import com.xqboss.apps.enums.sys.SaleableEnum;
import com.xqboss.apps.enums.user.UserWalletEnum;
import com.xqboss.apps.enums.user.WalletChangeDirectionEnum;
import com.xqboss.apps.mapper.marketing.MarketingAirdropMapper;
import com.xqboss.apps.service.box.BoxService;
import com.xqboss.apps.service.nft.NftService;
import com.xqboss.apps.service.user.SysUserWalletService;
import com.xqboss.common.core.domain.entity.SysUser;
import com.xqboss.common.core.mq.MqSender;
import com.xqboss.common.core.redis.RedisCache;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.exception.ServiceException;
import com.xqboss.common.utils.SecurityUtils;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.common.utils.sql.SqlUtil;
import com.xqboss.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 空投服务
 * </p>
 *
 * @author Trent
 * @date 2023/9/1
 */
@Service
public class AirdropService extends ServiceImpl<MarketingAirdropMapper, MarketingAirdrop> {

    @Autowired
    private NftService nftService;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private AirdropRecordService airdropRecordService;
    @Autowired
    private RedisCache redisCache;
    @Lazy
    @Autowired
    private MqSender mqSender;
    @Autowired
    private BoxService boxService;
    @Autowired
    private MarketingAirdropMaterialService marketingAirdropMaterialService;
    @Autowired
    private MarketingAirdropMaterialRecordService marketingAirdropMaterialRecordService;
    @Autowired
    private SysUserWalletService sysUserWalletService;

    /**
     * 发放空投
     */
    @Transactional(rollbackFor = Exception.class)
    public void  addAirdrop(AddAirdropDto dto) {
        //空投用户去重校验
        boolean hasDuplicates = dto.getUserList().stream()
                .distinct()
                .count() != dto.getUserList().size();
        if (hasDuplicates) {
            throw new ServiceException("用户列表重复");
        }
        //空投数量
        int quantity = dto.getQuantity();

        // 存储空投记录
        MarketingAirdrop airdrop = new MarketingAirdrop();
        switch (dto.getAirdropType()) {
            case EQUIPMENT:
            case NORMAL:
                // 判断藏品是否存在，
                Nft nft = nftService.getById(dto.getSourceId());
                if (Objects.isNull(nft)) {
                    throw new ServiceException("典藏不存在！");
                }
                //判断藏品库存是否充足
                if (nft.getStock() < dto.getUserList().size() * quantity) {
                    throw new ServiceException("典藏库存不足！");
                }
                // 扣除藏品实际库存s
                nftService.lockStock(nft.getId(), dto.getUserList().size() * quantity);
                airdrop.setAirdropName(nft.getTitle());
                airdrop.setAirdropImg(nft.getMainImg());
                airdrop.setPublishName(nft.getPublishName());
                break;
            case BOX:
                // 判断箱子是否存在，
                Box box = boxService.getById(dto.getSourceId());
                if (Objects.isNull(box)) {
                    throw new ServiceException("箱子不存在！");
                }
                //判断箱子库存是否充足
                if (box.getStock() < dto.getUserList().size() * quantity) {
                    throw new ServiceException("箱子库存不足！");
                }
                //判断箱子上架中无法空投
                if (box.getSaleable().equals(SaleableEnum.SEQUENCE.getValue())) {
                    throw new ServiceException("箱子上架状态无法空投！");
                }
                // 锁定箱子库存
                boxService.lockStock(box.getId(), dto.getUserList().size() * quantity);
                airdrop.setAirdropName(box.getName());
                airdrop.setAirdropImg(box.getHeadimg());
                airdrop.setPublishName(box.getPublishName());
                break;
            default:
                throw new ServiceException("空投藏品类型错误！");
        }
        // 判断用户列表是否都存在？
        List<Long> userIds = new ArrayList<>();
        if (dto.getUserType() == AirdropUserTypeEnum.USER_ID) {
            for (String userId : dto.getUserList()) {
                try {
                    SysUser sysUser = userService.selectUserById(Long.parseLong(userId));
                    Objects.requireNonNull(sysUser);
                    userIds.add(sysUser.getUserId());
                } catch (Exception e) {
                    throw new ServiceException(StringUtils.format("{}用户不存在！", userId));
                }
            }
        } else if (dto.getUserType() == AirdropUserTypeEnum.USER_PHONE) {
            for (String userPhone : dto.getUserList()) {
                try {
                    SysUser sysUser = userService.selectUserByUserName(userPhone);
                    Objects.requireNonNull(sysUser);
                    userIds.add(sysUser.getUserId());
                } catch (Exception e) {
                    throw new ServiceException(StringUtils.format("{}用户不存在！", userPhone));
                }
            }
        }


        airdrop.setId(IdUtil.getSnowflakeNextId());
        airdrop.setSourceId(dto.getSourceId());
        airdrop.setAirdropType(dto.getAirdropType());
        airdrop.setReason(dto.getReason());
        airdrop.setAirdropStatus(AirdropStatusEnum.NORMAL);
        airdrop.setUserSize(userIds.size());
        airdrop.setAirdropTime(dto.getAirdropTime());
        airdrop.setCreateUserId(SecurityUtils.getUserId());
        airdrop.setCreateUserName(SecurityUtils.getUsername());
        airdrop.setQuantity(quantity);
        boolean save = save(airdrop);
        if (!save) {
            throw new ServiceException("操作失败！");
        }
        // 存储空投记录
        List<MarketingAirdropRecord> recordList = new ArrayList<>();
        for (Long userId : userIds) {
            MarketingAirdropRecord record = new MarketingAirdropRecord();
            record.setId(IdUtil.getSnowflakeNextId());
            record.setUserId(userId);
            record.setAirdropId(airdrop.getId());
            record.setQuantity(quantity);
            record.setAirdropStatus(AirdropStatusEnum.NORMAL);
            recordList.add(record);
        }
        boolean saved = airdropRecordService.saveBatch(recordList);
        if(!saved){
            throw new ServiceException("操作失败！");
        }
        // 存未读缓存
        for (Long userId : userIds) {
            redisCache.increment(RedisKey.KEY_USER_UNREAD_GIFT + userId);
        }

        long delaySecond = 1;
        if(Objects.nonNull(dto.getAirdropTime())){
            long second = LocalDateTimeUtil.between(LocalDateTime.now(), dto.getAirdropTime(), ChronoUnit.SECONDS);
            if(second > 0) {
                delaySecond = second;
            }
        }
        // 记录空投顺序是随机还是顺序（只针对装配、藏品）到缓存
        redisCache.setCacheObject(RedisKey.KEY_AIRDROP_ID + airdrop.getId(), dto.getNftSequence());
        mqSender.sendDelay(AppMqDelayEnum.AIRDROP_START, airdrop.getId(), delaySecond);
    }

    /**
     * 删除空投（仅支持未发放的空投记录）
     */
    @RedisLock(lockKey = LockKey.LOCK_AIRDROP)
    @Transactional(rollbackFor = Exception.class)
    public void removeAirdrop(@LockValue Long id) {
        MarketingAirdrop airdrop = getById(id);
        if(Objects.isNull(airdrop)){
            throw new ServiceException("空投不存在！");
        }
        if(airdrop.getAirdropStatus() != AirdropStatusEnum.NORMAL){
            throw new ServiceException("空投已发放，无法删除！");
        }
        airdropRecordService.removeByAirdropId(airdrop.getId());
        boolean removed = removeById(id);
        if(!removed){
            throw new ServiceException("操作失败！");
        }
        nftService.unlockStock(airdrop.getSourceId(), airdrop.getUserSize() * airdrop.getQuantity());

        mqSender.cancelDelay(AppMqDelayEnum.AIRDROP_START, airdrop.getId());
    }


    /**
     * 获取空投列表
     *
     * @return
     */
    public List<MarketingAirdrop> getAirdropList(String search){
        LambdaQueryWrapper<MarketingAirdrop> queryWrapper = new LambdaQueryWrapper<>();
        if(StringUtils.isNotEmpty(search)){
            queryWrapper.like(MarketingAirdrop::getReason, search)
                    .or()
                    .eq(MarketingAirdrop::getSourceId, search)
                    .or()
                    .eq(MarketingAirdrop::getId, search);
        }
        List<MarketingAirdrop> list = list(queryWrapper);
        // 遍历获取NFT信息
        for (MarketingAirdrop airdrop : list) {
            Nft nft = nftService.getById(airdrop.getSourceId());
            airdrop.setNft(nft);
        }
        return list;
    }


    /**
     * 获取空投详情
     *
     * @return
     */
    public MarketingAirdrop getAirdropDetail(Long id){
        MarketingAirdrop airdrop = getById(id);
        Nft nft = nftService.getById(airdrop.getSourceId());
        airdrop.setNft(nft);
        return airdrop;
    }

    /**
     * 获取用户的空投记录
     * @param userId 用户ID
     */
    public void getRecordByUserId(Long userId){
    }

    /**
     * 发放空投
     */
    public void dispatchAirdrop() {
        List<MarketingAirdrop> airdropList = list(new LambdaQueryWrapper<MarketingAirdrop>()
                .eq(MarketingAirdrop::getAirdropStatus, AirdropStatusEnum.NORMAL)
                .le(MarketingAirdrop::getAirdropTime, LocalDateTime.now()));
        for (MarketingAirdrop airdrop : airdropList) {
            try{
                dispatchAirdrop(airdrop.getId(),Boolean.TRUE);//装备|藏品的默认空投顺序为随机的
            }catch (Exception e){
            }
        }
    }

    @RedisLock(lockKey = LockKey.LOCK_AIRDROP)
    public void dispatchAirdrop(@LockValue Long airdropId,boolean isRandom){
        MarketingAirdrop airdrop = getById(airdropId);
        if(airdrop == null || airdrop.getAirdropStatus() != AirdropStatusEnum.NORMAL){
            return;
        }
        boolean update = update(new LambdaUpdateWrapper<MarketingAirdrop>()
                .eq(MarketingAirdrop::getId, airdropId)
                .set(MarketingAirdrop::getAirdropStatus, AirdropStatusEnum.COMPLETE));
        if(!update){
            throw new ServiceException("发放失败！");
        }
        if(airdrop.getAirdropStatus() == AirdropStatusEnum.NORMAL){
            airdropRecordService.dispatchAirdrop(airdrop,isRandom);
        }
    }

    /**
     * 添加空投材料
     */
    @Transactional(rollbackFor = Exception.class)
    public void addAirdropMaterial(WebAirdropMaterialDto dto) {
        //收集所有的用户ID
        List<Long> userIds = dto.getUserList().stream().map(WebAirdropMaterialUserDto::getUserId).collect(Collectors.toList());
        //找出重复的用户ID
        List<Long> duplicateUserIds = userIds.stream()
                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                .entrySet().stream()
                .filter(entry -> entry.getValue() > 1)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        //如果存在重复的用户ID，抛出异常
        if (!duplicateUserIds.isEmpty()) {
            throw new ServiceException("用户列表重复，重复的用户ID为：" + duplicateUserIds);
        }
        //校验金额数量
        List<BigDecimal> amountList = dto.getUserList().stream().map(WebAirdropMaterialUserDto::getAmount).collect(Collectors.toList());
        if (amountList.stream().anyMatch(amount -> amount.compareTo(BigDecimal.ZERO) <= 0)) {
            throw new ServiceException("空投金额数量不能小于0");
        }
        // 校验空投类型如果不为热豆和扳手提示异常
        if (dto.getAirdropType() != UserWalletEnum.INTEGRAL && dto.getAirdropType() != UserWalletEnum.WRENCH) {
            throw new ServiceException("空投类型错误！");
        }
        // 判断用户列表是否都存在
        for (Long userId : userIds) {
            SysUser sysUser = userService.selectUserByIdAndStatus(userId);
            if (Objects.isNull(sysUser)) {
                throw new ServiceException(StringUtils.format("{}用户不存在！", userId));
            }
        }
        // 存储空投材料
        MarketingAirdropMaterial material = new MarketingAirdropMaterial();
        material.setType(dto.getAirdropType());
        material.setTotalAmount(amountList.stream().reduce(BigDecimal.ZERO, BigDecimal::add));
        material.setTotalNum(userIds.size());
        material.setReason(dto.getReason());
        material.setDirection(dto.getDirection());
        material.setOptUserId(SecurityUtils.getUserId());
        material.setOptName(SecurityUtils.getUsername());
        marketingAirdropMaterialService.save(material);
        // 存储每个用户的空投材料记录
        List<MarketingAirdropMaterialRecord> materialArrayList = new ArrayList<>();
        dto.getUserList().forEach(item -> {
            MarketingAirdropMaterialRecord record = new MarketingAirdropMaterialRecord();
            record.setAirdropMaterialId(material.getId());
            record.setUserId(item.getUserId());
            record.setType(dto.getAirdropType());
            record.setAmount(item.getAmount());
            record.setDirection(dto.getDirection());
            materialArrayList.add(record);
        });
        marketingAirdropMaterialRecordService.saveBatch(materialArrayList);
        //增加对应用户的热豆或者扳手
        dto.getUserList().forEach(item -> {
            ChangeUserWalletDto walletDto = new ChangeUserWalletDto();
            walletDto.setUserId(item.getUserId());
            walletDto.setType(dto.getAirdropType());
            walletDto.setDirection(WalletChangeDirectionEnum.IN);
            walletDto.setAmount(item.getAmount());
            walletDto.setRemarks(dto.getReason());
            walletDto.setDirection(dto.getDirection());
            sysUserWalletService.handleWalletChange(walletDto);
        });
    }
}
